(function(root, factory) {
    if (typeof define === "function" && define.amd) {
        define(["d3"], function(d3) {
            return root.Rickshaw = factory(d3)
        })
    } else if (typeof exports === "object") {
        module.exports = factory(require("d3"))
    } else {
        root.Rickshaw = factory(d3)
    }
})(this, function(d3) {
    var Rickshaw = {
        namespace: function(namespace, obj) {
            var parts = namespace.split(".");
            var parent = Rickshaw;
            for (var i = 1, length = parts.length; i < length; i++) {
                var currentPart = parts[i];
                parent[currentPart] = parent[currentPart] || {};
                parent = parent[currentPart]
            }
            return parent
        },
        keys: function(obj) {
            var keys = [];
            for (var key in obj) keys.push(key);
            return keys
        },
        extend: function(destination, source) {
            for (var property in source) {
                destination[property] = source[property]
            }
            return destination
        },
        clone: function(obj) {
            return JSON.parse(JSON.stringify(obj))
        }
    };
    (function(globalContext) {
        var _toString = Object.prototype.toString,
            NULL_TYPE = "Null",
            UNDEFINED_TYPE = "Undefined",
            BOOLEAN_TYPE = "Boolean",
            NUMBER_TYPE = "Number",
            STRING_TYPE = "String",
            OBJECT_TYPE = "Object",
            FUNCTION_CLASS = "[object Function]";

        function isFunction(object) {
            return _toString.call(object) === FUNCTION_CLASS
        }

        function extend(destination, source) {
            for (var property in source)
                if (source.hasOwnProperty(property)) destination[property] = source[property];
            return destination
        }

        function keys(object) {
            if (Type(object) !== OBJECT_TYPE) {
                throw new TypeError
            }
            var results = [];
            for (var property in object) {
                if (object.hasOwnProperty(property)) {
                    results.push(property)
                }
            }
            return results
        }

        function Type(o) {
            switch (o) {
                case null:
                    return NULL_TYPE;
                case void 0:
                    return UNDEFINED_TYPE
            }
            var type = typeof o;
            switch (type) {
                case "boolean":
                    return BOOLEAN_TYPE;
                case "number":
                    return NUMBER_TYPE;
                case "string":
                    return STRING_TYPE
            }
            return OBJECT_TYPE
        }

        function isUndefined(object) {
            return typeof object === "undefined"
        }
        var slice = Array.prototype.slice;

        function argumentNames(fn) {
            var names = fn.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1].replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, "").replace(/\s+/g, "").split(",");
            return names.length == 1 && !names[0] ? [] : names
        }

        function wrap(fn, wrapper) {
            var __method = fn;
            return function() {
                var a = update([bind(__method, this)], arguments);
                return wrapper.apply(this, a)
            }
        }

        function update(array, args) {
            var arrayLength = array.length,
                length = args.length;
            while (length--) array[arrayLength + length] = args[length];
            return array
        }

        function merge(array, args) {
            array = slice.call(array, 0);
            return update(array, args)
        }

        function bind(fn, context) {
            if (arguments.length < 2 && isUndefined(arguments[0])) return this;
            var __method = fn,
                args = slice.call(arguments, 2);
            return function() {
                var a = merge(args, arguments);
                return __method.apply(context, a)
            }
        }
        var emptyFunction = function() {};
        var Class = function() {
            var IS_DONTENUM_BUGGY = function() {
                for (var p in {
                        toString: 1
                    }) {
                    if (p === "toString") return false
                }
                return true
            }();

            function subclass() {}

            function create() {
                var parent = null,
                    properties = [].slice.apply(arguments);
                if (isFunction(properties[0])) parent = properties.shift();

                function klass() {
                    this.initialize.apply(this, arguments)
                }
                extend(klass, Class.Methods);
                klass.superclass = parent;
                klass.subclasses = [];
                if (parent) {
                    subclass.prototype = parent.prototype;
                    klass.prototype = new subclass;
                    try {
                        parent.subclasses.push(klass)
                    } catch (e) {}
                }
                for (var i = 0, length = properties.length; i < length; i++) klass.addMethods(properties[i]);
                if (!klass.prototype.initialize) klass.prototype.initialize = emptyFunction;
                klass.prototype.constructor = klass;
                return klass
            }

            function addMethods(source) {
                var ancestor = this.superclass && this.superclass.prototype,
                    properties = keys(source);
                if (IS_DONTENUM_BUGGY) {
                    if (source.toString != Object.prototype.toString) properties.push("toString");
                    if (source.valueOf != Object.prototype.valueOf) properties.push("valueOf")
                }
                for (var i = 0, length = properties.length; i < length; i++) {
                    var property = properties[i],
                        value = source[property];
                    if (ancestor && isFunction(value) && argumentNames(value)[0] == "$super") {
                        var method = value;
                        value = wrap(function(m) {
                            return function() {
                                return ancestor[m].apply(this, arguments)
                            }
                        }(property), method);
                        value.valueOf = bind(method.valueOf, method);
                        value.toString = bind(method.toString, method)
                    }
                    this.prototype[property] = value
                }
                return this
            }
            return {
                create: create,
                Methods: {
                    addMethods: addMethods
                }
            }
        }();
        if (globalContext.exports) {
            globalContext.exports.Class = Class
        } else {
            globalContext.Class = Class
        }
    })(Rickshaw);
    Rickshaw.namespace("Rickshaw.Compat.ClassList");
    Rickshaw.Compat.ClassList = function() {
        if (typeof document !== "undefined" && !("classList" in document.createElement("a"))) {
            (function(view) {
                "use strict";
                var classListProp = "classList",
                    protoProp = "prototype",
                    elemCtrProto = (view.HTMLElement || view.Element)[protoProp],
                    objCtr = Object,
                    strTrim = String[protoProp].trim || function() {
                        return this.replace(/^\s+|\s+$/g, "")
                    },
                    arrIndexOf = Array[protoProp].indexOf || function(item) {
                        var i = 0,
                            len = this.length;
                        for (; i < len; i++) {
                            if (i in this && this[i] === item) {
                                return i
                            }
                        }
                        return -1
                    },
                    DOMEx = function(type, message) {
                        this.name = type;
                        this.code = DOMException[type];
                        this.message = message
                    },
                    checkTokenAndGetIndex = function(classList, token) {
                        if (token === "") {
                            throw new DOMEx("SYNTAX_ERR", "An invalid or illegal string was specified")
                        }
                        if (/\s/.test(token)) {
                            throw new DOMEx("INVALID_CHARACTER_ERR", "String contains an invalid character")
                        }
                        return arrIndexOf.call(classList, token)
                    },
                    ClassList = function(elem) {
                        var trimmedClasses = strTrim.call(elem.className),
                            classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [],
                            i = 0,
                            len = classes.length;
                        for (; i < len; i++) {
                            this.push(classes[i])
                        }
                        this._updateClassName = function() {
                            elem.className = this.toString()
                        }
                    },
                    classListProto = ClassList[protoProp] = [],
                    classListGetter = function() {
                        return new ClassList(this)
                    };
                DOMEx[protoProp] = Error[protoProp];
                classListProto.item = function(i) {
                    return this[i] || null
                };
                classListProto.contains = function(token) {
                    token += "";
                    return checkTokenAndGetIndex(this, token) !== -1
                };
                classListProto.add = function(token) {
                    token += "";
                    if (checkTokenAndGetIndex(this, token) === -1) {
                        this.push(token);
                        this._updateClassName()
                    }
                };
                classListProto.remove = function(token) {
                    token += "";
                    var index = checkTokenAndGetIndex(this, token);
                    if (index !== -1) {
                        this.splice(index, 1);
                        this._updateClassName()
                    }
                };
                classListProto.toggle = function(token) {
                    token += "";
                    if (checkTokenAndGetIndex(this, token) === -1) {
                        this.add(token)
                    } else {
                        this.remove(token)
                    }
                };
                classListProto.toString = function() {
                    return this.join(" ")
                };
                if (objCtr.defineProperty) {
                    var classListPropDesc = {
                        get: classListGetter,
                        enumerable: true,
                        configurable: true
                    };
                    try {
                        objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc)
                    } catch (ex) {
                        if (ex.number === -2146823252) {
                            classListPropDesc.enumerable = false;
                            objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc)
                        }
                    }
                } else if (objCtr[protoProp].__defineGetter__) {
                    elemCtrProto.__defineGetter__(classListProp, classListGetter)
                }
            })(window)
        }
    };
    if (typeof RICKSHAW_NO_COMPAT !== "undefined" && !RICKSHAW_NO_COMPAT || typeof RICKSHAW_NO_COMPAT === "undefined") {
        new Rickshaw.Compat.ClassList
    }
    Rickshaw.namespace("Rickshaw.Graph");
    Rickshaw.Graph = function(args) {
        var self = this;
        this.initialize = function(args) {
            if (!args.element) throw "Rickshaw.Graph needs a reference to an element";
            if (args.element.nodeType !== 1) throw "Rickshaw.Graph element was defined but not an HTML element";
            this.element = args.element;
            this.series = args.series;
            this.window = {};
            this.updateCallbacks = [];
            this.configureCallbacks = [];
            this.defaults = {
                interpolation: "cardinal",
                offset: "zero",
                min: undefined,
                max: undefined,
                preserve: false,
                xScale: undefined,
                yScale: undefined,
                stack: true
            };
            this._loadRenderers();
            this.configure(args);
            this.validateSeries(args.series);
            this.series.active = function() {
                return self.series.filter(function(s) {
                    return !s.disabled
                })
            };
            this.setSize({
                width: args.width,
                height: args.height
            });
            this.element.classList.add("rickshaw_graph");
            this.vis = d3.select(this.element).append("svg:svg").attr("width", this.width).attr("height", this.height);
            this.discoverRange()
        };
        this._loadRenderers = function() {
            for (var name in Rickshaw.Graph.Renderer) {
                if (!name || !Rickshaw.Graph.Renderer.hasOwnProperty(name)) continue;
                var r = Rickshaw.Graph.Renderer[name];
                if (!r || !r.prototype || !r.prototype.render) continue;
                self.registerRenderer(new r({
                    graph: self
                }))
            }
        };
        this.validateSeries = function(series) {
            if (!Array.isArray(series) && !(series instanceof Rickshaw.Series)) {
                var seriesSignature = Object.prototype.toString.apply(series);
                throw "series is not an array: " + seriesSignature
            }
            var pointsCount;
            series.forEach(function(s) {
                if (!(s instanceof Object)) {
                    throw "series element is not an object: " + s
                }
                if (!s.data) {
                    throw "series has no data: " + JSON.stringify(s)
                }
                if (!Array.isArray(s.data)) {
                    throw "series data is not an array: " + JSON.stringify(s.data)
                }
                if (s.data.length > 0) {
                    var x = s.data[0].x;
                    var y = s.data[0].y;
                    if (typeof x != "number" || typeof y != "number" && y !== null) {
                        throw "x and y properties of points should be numbers instead of " + typeof x + " and " + typeof y
                    }
                }
                if (s.data.length >= 3) {
                    if (s.data[2].x < s.data[1].x || s.data[1].x < s.data[0].x || s.data[s.data.length - 1].x < s.data[0].x) {
                        throw "series data needs to be sorted on x values for series name: " + s.name
                    }
                }
            }, this)
        };
        this.dataDomain = function() {
            var data = this.series.map(function(s) {
                return s.data
            });
            var min = d3.min(data.map(function(d) {
                return d[0].x
            }));
            var max = d3.max(data.map(function(d) {
                return d[d.length - 1].x
            }));
            return [min, max]
        };
        this.discoverRange = function() {
            var domain = this.renderer.domain();
            this.x = (this.xScale || d3.scale.linear()).copy().domain(domain.x).range([0, this.width]);
            this.y = (this.yScale || d3.scale.linear()).copy().domain(domain.y).range([this.height, 0]);
            this.x.magnitude = d3.scale.linear().domain([domain.x[0] - domain.x[0], domain.x[1] - domain.x[0]]).range([0, this.width]);
            this.y.magnitude = d3.scale.linear().domain([domain.y[0] - domain.y[0], domain.y[1] - domain.y[0]]).range([0, this.height])
        };
        this.render = function() {
            var stackedData = this.stackData();
            this.discoverRange();
            this.renderer.render();
            this.updateCallbacks.forEach(function(callback) {
                callback()
            })
        };
        this.update = this.render;
        this.stackData = function() {
            var data = this.series.active().map(function(d) {
                return d.data
            }).map(function(d) {
                return d.filter(function(d) {
                    return this._slice(d)
                }, this)
            }, this);
            var preserve = this.preserve;
            if (!preserve) {
                this.series.forEach(function(series) {
                    if (series.scale) {
                        preserve = true
                    }
                })
            }
            data = preserve ? Rickshaw.clone(data) : data;
            this.series.active().forEach(function(series, index) {
                if (series.scale) {
                    var seriesData = data[index];
                    if (seriesData) {
                        seriesData.forEach(function(d) {
                            d.y = series.scale(d.y)
                        })
                    }
                }
            });
            this.stackData.hooks.data.forEach(function(entry) {
                data = entry.f.apply(self, [data])
            });
            var stackedData;
            if (!this.renderer.unstack) {
                this._validateStackable();
                var layout = d3.layout.stack();
                layout.offset(self.offset);
                stackedData = layout(data)
            }
            stackedData = stackedData || data;
            if (this.renderer.unstack) {
                stackedData.forEach(function(seriesData) {
                    seriesData.forEach(function(d) {
                        d.y0 = d.y0 === undefined ? 0 : d.y0
                    })
                })
            }
            this.stackData.hooks.after.forEach(function(entry) {
                stackedData = entry.f.apply(self, [data])
            });
            var i = 0;
            this.series.forEach(function(series) {
                if (series.disabled) return;
                series.stack = stackedData[i++]
            });
            this.stackedData = stackedData;
            return stackedData
        };
        this._validateStackable = function() {
            var series = this.series;
            var pointsCount;
            series.forEach(function(s) {
                pointsCount = pointsCount || s.data.length;
                if (pointsCount && s.data.length != pointsCount) {
                    throw "stacked series cannot have differing numbers of points: " + pointsCount + " vs " + s.data.length + "; see Rickshaw.Series.fill()"
                }
            }, this)
        };
        this.stackData.hooks = {
            data: [],
            after: []
        };
        this._slice = function(d) {
            if (this.window.xMin || this.window.xMax) {
                var isInRange = true;
                if (this.window.xMin && d.x < this.window.xMin) isInRange = false;
                if (this.window.xMax && d.x > this.window.xMax) isInRange = false;
                return isInRange
            }
            return true
        };
        this.onUpdate = function(callback) {
            this.updateCallbacks.push(callback)
        };
        this.onConfigure = function(callback) {
            this.configureCallbacks.push(callback)
        };
        this.registerRenderer = function(renderer) {
            this._renderers = this._renderers || {};
            this._renderers[renderer.name] = renderer
        };
        this.configure = function(args) {
            this.config = this.config || {};
            if (args.width || args.height) {
                this.setSize(args)
            }
            Rickshaw.keys(this.defaults).forEach(function(k) {
                this.config[k] = k in args ? args[k] : k in this ? this[k] : this.defaults[k]
            }, this);
            Rickshaw.keys(this.config).forEach(function(k) {
                this[k] = this.config[k]
            }, this);
            if ("stack" in args) args.unstack = !args.stack;
            var renderer = args.renderer || this.renderer && this.renderer.name || "stack";
            this.setRenderer(renderer, args);
            this.configureCallbacks.forEach(function(callback) {
                callback(args)
            })
        };
        this.setRenderer = function(r, args) {
            if (typeof r == "function") {
                this.renderer = new r({
                    graph: self
                });
                this.registerRenderer(this.renderer)
            } else {
                if (!this._renderers[r]) {
                    throw "couldn't find renderer " + r
                }
                this.renderer = this._renderers[r]
            }
            if (typeof args == "object") {
                this.renderer.configure(args)
            }
        };
        this.setSize = function(args) {
            args = args || {};
            if (typeof window !== undefined) {
                var style = window.getComputedStyle(this.element, null);
                var elementWidth = parseInt(style.getPropertyValue("width"), 10);
                var elementHeight = parseInt(style.getPropertyValue("height"), 10)
            }
            this.width = args.width || elementWidth || 400;
            this.height = args.height || elementHeight || 250;
            this.vis && this.vis.attr("width", this.width).attr("height", this.height)
        };
        this.initialize(args)
    };
    Rickshaw.namespace("Rickshaw.Fixtures.Color");
    Rickshaw.Fixtures.Color = function() {
        this.schemes = {};
        this.schemes.spectrum14 = ["#ecb796", "#dc8f70", "#b2a470", "#92875a", "#716c49", "#d2ed82", "#bbe468", "#a1d05d", "#e7cbe6", "#d8aad6", "#a888c2", "#9dc2d3", "#649eb9", "#387aa3"].reverse();
        this.schemes.spectrum2000 = ["#57306f", "#514c76", "#646583", "#738394", "#6b9c7d", "#84b665", "#a7ca50", "#bfe746", "#e2f528", "#fff726", "#ecdd00", "#d4b11d", "#de8800", "#de4800", "#c91515", "#9a0000", "#7b0429", "#580839", "#31082b"];
        this.schemes.spectrum2001 = ["#2f243f", "#3c2c55", "#4a3768", "#565270", "#6b6b7c", "#72957f", "#86ad6e", "#a1bc5e", "#b8d954", "#d3e04e", "#ccad2a", "#cc8412", "#c1521d", "#ad3821", "#8a1010", "#681717", "#531e1e", "#3d1818", "#320a1b"];
        this.schemes.classic9 = ["#423d4f", "#4a6860", "#848f39", "#a2b73c", "#ddcb53", "#c5a32f", "#7d5836", "#963b20", "#7c2626", "#491d37", "#2f254a"].reverse();
        this.schemes.httpStatus = {
            503: "#ea5029",
            502: "#d23f14",
            500: "#bf3613",
            410: "#efacea",
            409: "#e291dc",
            403: "#f457e8",
            408: "#e121d2",
            401: "#b92dae",
            405: "#f47ceb",
            404: "#a82a9f",
            400: "#b263c6",
            301: "#6fa024",
            302: "#87c32b",
            307: "#a0d84c",
            304: "#28b55c",
            200: "#1a4f74",
            206: "#27839f",
            201: "#52adc9",
            202: "#7c979f",
            203: "#a5b8bd",
            204: "#c1cdd1"
        };
        this.schemes.colorwheel = ["#b5b6a9", "#858772", "#785f43", "#96557e", "#4682b4", "#65b9ac", "#73c03a", "#cb513a"].reverse();
        this.schemes.cool = ["#5e9d2f", "#73c03a", "#4682b4", "#7bc3b8", "#a9884e", "#c1b266", "#a47493", "#c09fb5"];
        this.schemes.munin = ["#00cc00", "#0066b3", "#ff8000", "#ffcc00", "#330099", "#990099", "#ccff00", "#ff0000", "#808080", "#008f00", "#00487d", "#b35a00", "#b38f00", "#6b006b", "#8fb300", "#b30000", "#bebebe", "#80ff80", "#80c9ff", "#ffc080", "#ffe680", "#aa80ff", "#ee00cc", "#ff8080", "#666600", "#ffbfff", "#00ffcc", "#cc6699", "#999900"]
    };
    Rickshaw.namespace("Rickshaw.Fixtures.RandomData");
    Rickshaw.Fixtures.RandomData = function(timeInterval) {
        var addData;
        timeInterval = timeInterval || 1;
        var lastRandomValue = 200;
        var timeBase = Math.floor((new Date).getTime() / 1e3);
        this.addData = function(data) {
            var randomValue = Math.random() * 100 + 15 + lastRandomValue;
            var index = data[0].length;
            var counter = 1;
            data.forEach(function(series) {
                var randomVariance = Math.random() * 20;
                var v = randomValue / 25 + counter++ + (Math.cos(index * counter * 11 / 960) + 2) * 15 + (Math.cos(index / 7) + 2) * 7 + (Math.cos(index / 17) + 2) * 1;
                series.push({
                    x: index * timeInterval + timeBase,
                    y: v + randomVariance
                })
            });
            lastRandomValue = randomValue * .85
        };
        this.removeData = function(data) {
            data.forEach(function(series) {
                series.shift()
            });
            timeBase += timeInterval
        }
    };
    Rickshaw.namespace("Rickshaw.Fixtures.Time");
    Rickshaw.Fixtures.Time = function() {
        var self = this;
        this.months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
        this.units = [{
            name: "decade",
            seconds: 86400 * 365.25 * 10,
            formatter: function(d) {
                return parseInt(d.getUTCFullYear() / 10, 10) * 10
            }
        }, {
            name: "year",
            seconds: 86400 * 365.25,
            formatter: function(d) {
                return d.getUTCFullYear()
            }
        }, {
            name: "month",
            seconds: 86400 * 30.5,
            formatter: function(d) {
                return self.months[d.getUTCMonth()]
            }
        }, {
            name: "week",
            seconds: 86400 * 7,
            formatter: function(d) {
                return self.formatDate(d)
            }
        }, {
            name: "day",
            seconds: 86400,
            formatter: function(d) {
                return d.getUTCDate()
            }
        }, {
            name: "6 hour",
            seconds: 3600 * 6,
            formatter: function(d) {
                return self.formatTime(d)
            }
        }, {
            name: "hour",
            seconds: 3600,
            formatter: function(d) {
                return self.formatTime(d)
            }
        }, {
            name: "15 minute",
            seconds: 60 * 15,
            formatter: function(d) {
                return self.formatTime(d)
            }
        }, {
            name: "minute",
            seconds: 60,
            formatter: function(d) {
                return d.getUTCMinutes()
            }
        }, {
            name: "15 second",
            seconds: 15,
            formatter: function(d) {
                return d.getUTCSeconds() + "s"
            }
        }, {
            name: "second",
            seconds: 1,
            formatter: function(d) {
                return d.getUTCSeconds() + "s"
            }
        }, {
            name: "decisecond",
            seconds: 1 / 10,
            formatter: function(d) {
                return d.getUTCMilliseconds() + "ms"
            }
        }, {
            name: "centisecond",
            seconds: 1 / 100,
            formatter: function(d) {
                return d.getUTCMilliseconds() + "ms"
            }
        }];
        this.unit = function(unitName) {
            return this.units.filter(function(unit) {
                return unitName == unit.name
            }).shift()
        };
        this.formatDate = function(d) {
            return d3.time.format("%b %e")(d)
        };
        this.formatTime = function(d) {
            return d.toUTCString().match(/(\d+:\d+):/)[1]
        };
        this.ceil = function(time, unit) {
            var date, floor, year;
            if (unit.name == "month") {
                date = new Date(time * 1e3);
                floor = Date.UTC(date.getUTCFullYear(), date.getUTCMonth()) / 1e3;
                if (floor == time) return time;
                year = date.getUTCFullYear();
                var month = date.getUTCMonth();
                if (month == 11) {
                    month = 0;
                    year = year + 1
                } else {
                    month += 1
                }
                return Date.UTC(year, month) / 1e3
            }
            if (unit.name == "year") {
                date = new Date(time * 1e3);
                floor = Date.UTC(date.getUTCFullYear(), 0) / 1e3;
                if (floor == time) return time;
                year = date.getUTCFullYear() + 1;
                return Date.UTC(year, 0) / 1e3
            }
            return Math.ceil(time / unit.seconds) * unit.seconds
        }
    };
    Rickshaw.namespace("Rickshaw.Fixtures.Time.Local");
    Rickshaw.Fixtures.Time.Local = function() {
        var self = this;
        this.months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
        this.units = [{
            name: "decade",
            seconds: 86400 * 365.25 * 10,
            formatter: function(d) {
                return parseInt(d.getFullYear() / 10, 10) * 10
            }
        }, {
            name: "year",
            seconds: 86400 * 365.25,
            formatter: function(d) {
                return d.getFullYear()
            }
        }, {
            name: "month",
            seconds: 86400 * 30.5,
            formatter: function(d) {
                return self.months[d.getMonth()]
            }
        }, {
            name: "week",
            seconds: 86400 * 7,
            formatter: function(d) {
                return self.formatDate(d)
            }
        }, {
            name: "day",
            seconds: 86400,
            formatter: function(d) {
                return d.getDate()
            }
        }, {
            name: "6 hour",
            seconds: 3600 * 6,
            formatter: function(d) {
                return self.formatTime(d)
            }
        }, {
            name: "hour",
            seconds: 3600,
            formatter: function(d) {
                return self.formatTime(d)
            }
        }, {
            name: "15 minute",
            seconds: 60 * 15,
            formatter: function(d) {
                return self.formatTime(d)
            }
        }, {
            name: "minute",
            seconds: 60,
            formatter: function(d) {
                return d.getMinutes()
            }
        }, {
            name: "15 second",
            seconds: 15,
            formatter: function(d) {
                return d.getSeconds() + "s"
            }
        }, {
            name: "second",
            seconds: 1,
            formatter: function(d) {
                return d.getSeconds() + "s"
            }
        }, {
            name: "decisecond",
            seconds: 1 / 10,
            formatter: function(d) {
                return d.getMilliseconds() + "ms"
            }
        }, {
            name: "centisecond",
            seconds: 1 / 100,
            formatter: function(d) {
                return d.getMilliseconds() + "ms"
            }
        }];
        this.unit = function(unitName) {
            return this.units.filter(function(unit) {
                return unitName == unit.name
            }).shift()
        };
        this.formatDate = function(d) {
            return d3.time.format("%b %e")(d)
        };
        this.formatTime = function(d) {
            return d.toString().match(/(\d+:\d+):/)[1]
        };
        this.ceil = function(time, unit) {
            var date, floor, year;
            if (unit.name == "day") {
                var nearFuture = new Date((time + unit.seconds - 1) * 1e3);
                var rounded = new Date(0);
                rounded.setMilliseconds(0);
                rounded.setSeconds(0);
                rounded.setMinutes(0);
                rounded.setHours(0);
                rounded.setDate(nearFuture.getDate());
                rounded.setMonth(nearFuture.getMonth());
                rounded.setFullYear(nearFuture.getFullYear());
                return rounded.getTime() / 1e3
            }
            if (unit.name == "month") {
                date = new Date(time * 1e3);
                floor = new Date(date.getFullYear(), date.getMonth()).getTime() / 1e3;
                if (floor == time) return time;
                year = date.getFullYear();
                var month = date.getMonth();
                if (month == 11) {
                    month = 0;
                    year = year + 1
                } else {
                    month += 1
                }
                return new Date(year, month).getTime() / 1e3
            }
            if (unit.name == "year") {
                date = new Date(time * 1e3);
                floor = new Date(date.getUTCFullYear(), 0).getTime() / 1e3;
                if (floor == time) return time;
                year = date.getFullYear() + 1;
                return new Date(year, 0).getTime() / 1e3
            }
            return Math.ceil(time / unit.seconds) * unit.seconds
        }
    };
    Rickshaw.namespace("Rickshaw.Fixtures.Number");
    Rickshaw.Fixtures.Number.formatKMBT = function(y) {
        var abs_y = Math.abs(y);
        if (abs_y >= 1e12) {
            return y / 1e12 + "T"
        } else if (abs_y >= 1e9) {
            return y / 1e9 + "B"
        } else if (abs_y >= 1e6) {
            return y / 1e6 + "M"
        } else if (abs_y >= 1e3) {
            return y / 1e3 + "K"
        } else if (abs_y < 1 && y > 0) {
            return y.toFixed(2)
        } else if (abs_y === 0) {
            return ""
        } else {
            return y
        }
    };
    Rickshaw.Fixtures.Number.formatBase1024KMGTP = function(y) {
        var abs_y = Math.abs(y);
        if (abs_y >= 0x4000000000000) {
            return y / 0x4000000000000 + "P"
        } else if (abs_y >= 1099511627776) {
            return y / 1099511627776 + "T"
        } else if (abs_y >= 1073741824) {
            return y / 1073741824 + "G"
        } else if (abs_y >= 1048576) {
            return y / 1048576 + "M"
        } else if (abs_y >= 1024) {
            return y / 1024 + "K"
        } else if (abs_y < 1 && y > 0) {
            return y.toFixed(2)
        } else if (abs_y === 0) {
            return ""
        } else {
            return y
        }
    };
    Rickshaw.namespace("Rickshaw.Color.Palette");
    Rickshaw.Color.Palette = function(args) {
        var color = new Rickshaw.Fixtures.Color;
        args = args || {};
        this.schemes = {};
        this.scheme = color.schemes[args.scheme] || args.scheme || color.schemes.colorwheel;
        this.runningIndex = 0;
        this.generatorIndex = 0;
        if (args.interpolatedStopCount) {
            var schemeCount = this.scheme.length - 1;
            var i, j, scheme = [];
            for (i = 0; i < schemeCount; i++) {
                scheme.push(this.scheme[i]);
                var generator = d3.interpolateHsl(this.scheme[i], this.scheme[i + 1]);
                for (j = 1; j < args.interpolatedStopCount; j++) {
                    scheme.push(generator(1 / args.interpolatedStopCount * j))
                }
            }
            scheme.push(this.scheme[this.scheme.length - 1]);
            this.scheme = scheme
        }
        this.rotateCount = this.scheme.length;
        this.color = function(key) {
            return this.scheme[key] || this.scheme[this.runningIndex++] || this.interpolateColor() || "#808080"
        };
        this.interpolateColor = function() {
            if (!Array.isArray(this.scheme)) return;
            var color;
            if (this.generatorIndex == this.rotateCount * 2 - 1) {
                color = d3.interpolateHsl(this.scheme[this.generatorIndex], this.scheme[0])(.5);
                this.generatorIndex = 0;
                this.rotateCount *= 2
            } else {
                color = d3.interpolateHsl(this.scheme[this.generatorIndex], this.scheme[this.generatorIndex + 1])(.5);
                this.generatorIndex++
            }
            this.scheme.push(color);
            return color
        }
    };
    Rickshaw.namespace("Rickshaw.Graph.Ajax");
    Rickshaw.Graph.Ajax = Rickshaw.Class.create({
        initialize: function(args) {
            this.dataURL = args.dataURL;
            this.onData = args.onData || function(d) {
                return d
            };
            this.onComplete = args.onComplete || function() {};
            this.onError = args.onError || function() {};
            this.args = args;
            this.request()
        },
        request: function() {
            jQuery.ajax({
                url: this.dataURL,
                dataType: "json",
                success: this.success.bind(this),
                error: this.error.bind(this)
            })
        },
        error: function() {
            console.log("error loading dataURL: " + this.dataURL);
            this.onError(this)
        },
        success: function(data, status) {
            data = this.onData(data);
            this.args.series = this._splice({
                data: data,
                series: this.args.series
            });
            this.graph = this.graph || new Rickshaw.Graph(this.args);
            this.graph.render();
            this.onComplete(this)
        },
        _splice: function(args) {
            var data = args.data;
            var series = args.series;
            if (!args.series) return data;
            series.forEach(function(s) {
                var seriesKey = s.key || s.name;
                if (!seriesKey) throw "series needs a key or a name";
                data.forEach(function(d) {
                    var dataKey = d.key || d.name;
                    if (!dataKey) throw "data needs a key or a name";
                    if (seriesKey == dataKey) {
                        var properties = ["color", "name", "data"];
                        properties.forEach(function(p) {
                            if (d[p]) s[p] = d[p]
                        })
                    }
                })
            });
            return series
        }
    });
    Rickshaw.namespace("Rickshaw.Graph.Annotate");
    Rickshaw.Graph.Annotate = function(args) {
        var graph = this.graph = args.graph;
        this.elements = {
            timeline: args.element
        };
        var self = this;
        this.data = {};
        this.elements.timeline.classList.add("rickshaw_annotation_timeline");
        this.add = function(time, content, end_time) {
            self.data[time] = self.data[time] || {
                boxes: []
            };
            self.data[time].boxes.push({
                content: content,
                end: end_time
            })
        };
        this.update = function() {
            Rickshaw.keys(self.data).forEach(function(time) {
                var annotation = self.data[time];
                var left = self.graph.x(time);
                if (left < 0 || left > self.graph.x.range()[1]) {
                    if (annotation.element) {
                        annotation.line.classList.add("offscreen");
                        annotation.element.style.display = "none"
                    }
                    annotation.boxes.forEach(function(box) {
                        if (box.rangeElement) box.rangeElement.classList.add("offscreen")
                    });
                    return
                }
                if (!annotation.element) {
                    var element = annotation.element = document.createElement("div");
                    element.classList.add("annotation");
                    this.elements.timeline.appendChild(element);
                    element.addEventListener("click", function(e) {
                        element.classList.toggle("active");
                        annotation.line.classList.toggle("active");
                        annotation.boxes.forEach(function(box) {
                            if (box.rangeElement) box.rangeElement.classList.toggle("active")
                        })
                    }, false)
                }
                annotation.element.style.left = left + "px";
                annotation.element.style.display = "block";
                annotation.boxes.forEach(function(box) {
                    var element = box.element;
                    if (!element) {
                        element = box.element = document.createElement("div");
                        element.classList.add("content");
                        element.innerHTML = box.content;
                        annotation.element.appendChild(element);
                        annotation.line = document.createElement("div");
                        annotation.line.classList.add("annotation_line");
                        self.graph.element.appendChild(annotation.line);
                        if (box.end) {
                            box.rangeElement = document.createElement("div");
                            box.rangeElement.classList.add("annotation_range");
                            self.graph.element.appendChild(box.rangeElement)
                        }
                    }
                    if (box.end) {
                        var annotationRangeStart = left;
                        var annotationRangeEnd = Math.min(self.graph.x(box.end), self.graph.x.range()[1]);
                        if (annotationRangeStart > annotationRangeEnd) {
                            annotationRangeEnd = left;
                            annotationRangeStart = Math.max(self.graph.x(box.end), self.graph.x.range()[0])
                        }
                        var annotationRangeWidth = annotationRangeEnd - annotationRangeStart;
                        box.rangeElement.style.left = annotationRangeStart + "px";
                        box.rangeElement.style.width = annotationRangeWidth + "px";
                        box.rangeElement.classList.remove("offscreen")
                    }
                    annotation.line.classList.remove("offscreen");
                    annotation.line.style.left = left + "px"
                })
            }, this)
        };
        this.graph.onUpdate(function() {
            self.update()
        })
    };
    Rickshaw.namespace("Rickshaw.Graph.Axis.Time");
    Rickshaw.Graph.Axis.Time = function(args) {
        var self = this;
        this.graph = args.graph;
        this.elements = [];
        this.ticksTreatment = args.ticksTreatment || "plain";
        this.fixedTimeUnit = args.timeUnit;
        var time = args.timeFixture || new Rickshaw.Fixtures.Time;
        this.appropriateTimeUnit = function() {
            var unit;
            var units = time.units;
            var domain = this.graph.x.domain();
            var rangeSeconds = domain[1] - domain[0];
            units.forEach(function(u) {
                if (Math.floor(rangeSeconds / u.seconds) >= 2) {
                    unit = unit || u
                }
            });
            return unit || time.units[time.units.length - 1]
        };
        this.tickOffsets = function() {
            var domain = this.graph.x.domain();
            var unit = this.fixedTimeUnit || this.appropriateTimeUnit();
            var count = Math.ceil((domain[1] - domain[0]) / unit.seconds);
            var runningTick = domain[0];
            var offsets = [];
            for (var i = 0; i < count; i++) {
                var tickValue = time.ceil(runningTick, unit);
                runningTick = tickValue + unit.seconds / 2;
                offsets.push({
                    value: tickValue,
                    unit: unit
                })
            }
            return offsets
        };
        this.render = function() {
            this.elements.forEach(function(e) {
                e.parentNode.removeChild(e)
            });
            this.elements = [];
            var offsets = this.tickOffsets();
            offsets.forEach(function(o) {
                if (self.graph.x(o.value) > self.graph.x.range()[1]) return;
                var element = document.createElement("div");
                element.style.left = self.graph.x(o.value) + "px";
                element.classList.add("x_tick");
                element.classList.add(self.ticksTreatment);
                var title = document.createElement("div");
                title.classList.add("title");
                title.innerHTML = o.unit.formatter(new Date(o.value * 1e3));
                element.appendChild(title);
                self.graph.element.appendChild(element);
                self.elements.push(element)
            })
        };
        this.graph.onUpdate(function() {
            self.render()
        })
    };
    Rickshaw.namespace("Rickshaw.Graph.Axis.X");
    Rickshaw.Graph.Axis.X = function(args) {
        var self = this;
        var berthRate = .1;
        this.initialize = function(args) {
            this.graph = args.graph;
            this.orientation = args.orientation || "top";
            this.pixelsPerTick = args.pixelsPerTick || 75;
            if (args.ticks) this.staticTicks = args.ticks;
            if (args.tickValues) this.tickValues = args.tickValues;
            this.tickSize = args.tickSize || 4;
            this.ticksTreatment = args.ticksTreatment || "plain";
            if (args.element) {
                this.element = args.element;
                this._discoverSize(args.element, args);
                this.vis = d3.select(args.element).append("svg:svg").attr("height", this.height).attr("width", this.width).attr("class", "rickshaw_graph x_axis_d3");
                this.element = this.vis[0][0];
                this.element.style.position = "relative";
                this.setSize({
                    width: args.width,
                    height: args.height
                })
            } else {
                this.vis = this.graph.vis
            }
            this.graph.onUpdate(function() {
                self.render()
            })
        };
        this.setSize = function(args) {
            args = args || {};
            if (!this.element) return;
            this._discoverSize(this.element.parentNode, args);
            this.vis.attr("height", this.height).attr("width", this.width * (1 + berthRate));
            var berth = Math.floor(this.width * berthRate / 2);
            this.element.style.left = -1 * berth + "px"
        };
        this.render = function() {
            if (this._renderWidth !== undefined && this.graph.width !== this._renderWidth) this.setSize({
                auto: true
            });
            var axis = d3.svg.axis().scale(this.graph.x).orient(this.orientation);
            axis.tickFormat(args.tickFormat || function(x) {
                return x
            });
            if (this.tickValues) axis.tickValues(this.tickValues);
            this.ticks = this.staticTicks || Math.floor(this.graph.width / this.pixelsPerTick);
            var berth = Math.floor(this.width * berthRate / 2) || 0;
            var transform;
            if (this.orientation == "top") {
                var yOffset = this.height || this.graph.height;
                transform = "translate(" + berth + "," + yOffset + ")"
            } else {
                transform = "translate(" + berth + ", 0)"
            }
            if (this.element) {
                this.vis.selectAll("*").remove()
            }
            this.vis.append("svg:g").attr("class", ["x_ticks_d3", this.ticksTreatment].join(" ")).attr("transform", transform).call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(this.tickSize));
            var gridSize = (this.orientation == "bottom" ? 1 : -1) * this.graph.height;
            this.graph.vis.append("svg:g").attr("class", "x_grid_d3").call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(gridSize)).selectAll("text").each(function() {
                this.parentNode.setAttribute("data-x-value", this.textContent)
            });
            this._renderHeight = this.graph.height
        };
        this._discoverSize = function(element, args) {
            if (typeof window !== "undefined") {
                var style = window.getComputedStyle(element, null);
                var elementHeight = parseInt(style.getPropertyValue("height"), 10);
                if (!args.auto) {
                    var elementWidth = parseInt(style.getPropertyValue("width"), 10)
                }
            }
            this.width = (args.width || elementWidth || this.graph.width) * (1 + berthRate);
            this.height = args.height || elementHeight || 40
        };
        this.initialize(args)
    };
    Rickshaw.namespace("Rickshaw.Graph.Axis.Y");
    Rickshaw.Graph.Axis.Y = Rickshaw.Class.create({
        initialize: function(args) {
            this.graph = args.graph;
            this.orientation = args.orientation || "right";
            this.pixelsPerTick = args.pixelsPerTick || 75;
            if (args.ticks) this.staticTicks = args.ticks;
            if (args.tickValues) this.tickValues = args.tickValues;
            this.tickSize = args.tickSize || 4;
            this.ticksTreatment = args.ticksTreatment || "plain";
            this.tickFormat = args.tickFormat || function(y) {
                return y
            };
            this.berthRate = .1;
            if (args.element) {
                this.element = args.element;
                this.vis = d3.select(args.element).append("svg:svg").attr("class", "rickshaw_graph y_axis");
                this.element = this.vis[0][0];
                this.element.style.position = "relative";
                this.setSize({
                    width: args.width,
                    height: args.height
                })
            } else {
                this.vis = this.graph.vis
            }
            var self = this;
            this.graph.onUpdate(function() {
                self.render()
            })
        },
        setSize: function(args) {
            args = args || {};
            if (!this.element) return;
            if (typeof window !== "undefined") {
                var style = window.getComputedStyle(this.element.parentNode, null);
                var elementWidth = parseInt(style.getPropertyValue("width"), 10);
                if (!args.auto) {
                    var elementHeight = parseInt(style.getPropertyValue("height"), 10)
                }
            }
            this.width = args.width || elementWidth || this.graph.width * this.berthRate;
            this.height = args.height || elementHeight || this.graph.height;
            this.vis.attr("width", this.width).attr("height", this.height * (1 + this.berthRate));
            var berth = this.height * this.berthRate;
            if (this.orientation == "left") {
                this.element.style.top = -1 * berth + "px"
            }
        },
        render: function() {
            if (this._renderHeight !== undefined && this.graph.height !== this._renderHeight) this.setSize({
                auto: true
            });
            this.ticks = this.staticTicks || Math.floor(this.graph.height / this.pixelsPerTick);
            var axis = this._drawAxis(this.graph.y);
            this._drawGrid(axis);
            this._renderHeight = this.graph.height
        },
        _drawAxis: function(scale) {
            var axis = d3.svg.axis().scale(scale).orient(this.orientation);
            axis.tickFormat(this.tickFormat);
            if (this.tickValues) axis.tickValues(this.tickValues);
            if (this.orientation == "left") {
                var berth = this.height * this.berthRate;
                var transform = "translate(" + this.width + ", " + berth + ")"
            }
            if (this.element) {
                this.vis.selectAll("*").remove()
            }
            this.vis.append("svg:g").attr("class", ["y_ticks", this.ticksTreatment].join(" ")).attr("transform", transform).call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(this.tickSize));
            return axis
        },
        _drawGrid: function(axis) {
            var gridSize = (this.orientation == "right" ? 1 : -1) * this.graph.width;
            this.graph.vis.append("svg:g").attr("class", "y_grid").call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(gridSize)).selectAll("text").each(function() {
                this.parentNode.setAttribute("data-y-value", this.textContent)
            })
        }
    });
    Rickshaw.namespace("Rickshaw.Graph.Axis.Y.Scaled");
    Rickshaw.Graph.Axis.Y.Scaled = Rickshaw.Class.create(Rickshaw.Graph.Axis.Y, {
        initialize: function($super, args) {
            if (typeof args.scale === "undefined") {
                throw new Error("Scaled requires scale")
            }
            this.scale = args.scale;
            if (typeof args.grid === "undefined") {
                this.grid = true
            } else {
                this.grid = args.grid
            }
            $super(args)
        },
        _drawAxis: function($super, scale) {
            var domain = this.scale.domain();
            var renderDomain = this.graph.renderer.domain().y;
            var extents = [Math.min.apply(Math, domain), Math.max.apply(Math, domain)];
            var extentMap = d3.scale.linear().domain([0, 1]).range(extents);
            var adjExtents = [extentMap(renderDomain[0]), extentMap(renderDomain[1])];
            var adjustment = d3.scale.linear().domain(extents).range(adjExtents);
            var adjustedScale = this.scale.copy().domain(domain.map(adjustment)).range(scale.range());
            return $super(adjustedScale)
        },
        _drawGrid: function($super, axis) {
            if (this.grid) {
                $super(axis)
            }
        }
    });
    Rickshaw.namespace("Rickshaw.Graph.Behavior.Series.Highlight");
    Rickshaw.Graph.Behavior.Series.Highlight = function(args) {
        this.graph = args.graph;
        this.legend = args.legend;
        var self = this;
        var colorSafe = {};
        var activeLine = null;
        var disabledColor = args.disabledColor || function(seriesColor) {
            return d3.interpolateRgb(seriesColor, d3.rgb("#d8d8d8"))(.8).toString()
        };
        this.addHighlightEvents = function(l) {
            l.element.addEventListener("mouseover", function(e) {
                if (activeLine) return;
                else activeLine = l;
                self.legend.lines.forEach(function(line) {
                    if (l === line) {
                        if (self.graph.renderer.unstack && (line.series.renderer ? line.series.renderer.unstack : true)) {
                            var seriesIndex = self.graph.series.indexOf(line.series);
                            line.originalIndex = seriesIndex;
                            var series = self.graph.series.splice(seriesIndex, 1)[0];
                            self.graph.series.push(series)
                        }
                        return
                    }
                    colorSafe[line.series.name] = colorSafe[line.series.name] || line.series.color;
                    line.series.color = disabledColor(line.series.color)
                });
                self.graph.update()
            }, false);
            l.element.addEventListener("mouseout", function(e) {
                if (!activeLine) return;
                else activeLine = null;
                self.legend.lines.forEach(function(line) {
                    if (l === line && line.hasOwnProperty("originalIndex")) {
                        var series = self.graph.series.pop();
                        self.graph.series.splice(line.originalIndex, 0, series);
                        delete line.originalIndex
                    }
                    if (colorSafe[line.series.name]) {
                        line.series.color = colorSafe[line.series.name]
                    }
                });
                self.graph.update()
            }, false)
        };
        if (this.legend) {
            this.legend.lines.forEach(function(l) {
                self.addHighlightEvents(l)
            })
        }
    };
    Rickshaw.namespace("Rickshaw.Graph.Behavior.Series.Order");
    Rickshaw.Graph.Behavior.Series.Order = function(args) {
        this.graph = args.graph;
        this.legend = args.legend;
        var self = this;
        if (typeof window.jQuery == "undefined") {
            throw "couldn't find jQuery at window.jQuery"
        }
        if (typeof window.jQuery.ui == "undefined") {
            throw "couldn't find jQuery UI at window.jQuery.ui"
        }
        jQuery(function() {
            jQuery(self.legend.list).sortable({
                containment: "parent",
                tolerance: "pointer",
                update: function(event, ui) {
                    var series = [];
                    jQuery(self.legend.list).find("li").each(function(index, item) {
                        if (!item.series) return;
                        series.push(item.series)
                    });
                    for (var i = self.graph.series.length - 1; i >= 0; i--) {
                        self.graph.series[i] = series.shift()
                    }
                    self.graph.update()
                }
            });
            jQuery(self.legend.list).disableSelection()
        });
        this.graph.onUpdate(function() {
            var h = window.getComputedStyle(self.legend.element).height;
            self.legend.element.style.height = h
        })
    };
    Rickshaw.namespace("Rickshaw.Graph.Behavior.Series.Toggle");
    Rickshaw.Graph.Behavior.Series.Toggle = function(args) {
        this.graph = args.graph;
        this.legend = args.legend;
        var self = this;
        this.addAnchor = function(line) {
            var anchor = document.createElement("a");
            anchor.innerHTML = "&#10004;";
            anchor.classList.add("action");
            line.element.insertBefore(anchor, line.element.firstChild);
            anchor.onclick = function(e) {
                if (line.series.disabled) {
                    line.series.enable();
                    line.element.classList.remove("disabled")
                } else {
                    if (this.graph.series.filter(function(s) {
                            return !s.disabled
                        }).length <= 1) return;
                    line.series.disable();
                    line.element.classList.add("disabled")
                }
                self.graph.update()
            }.bind(this);
            var label = line.element.getElementsByTagName("span")[0];
            label.onclick = function(e) {
                var disableAllOtherLines = line.series.disabled;
                if (!disableAllOtherLines) {
                    for (var i = 0; i < self.legend.lines.length; i++) {
                        var l = self.legend.lines[i];
                        if (line.series === l.series) {} else if (l.series.disabled) {} else {
                            disableAllOtherLines = true;
                            break
                        }
                    }
                }
                if (disableAllOtherLines) {
                    line.series.enable();
                    line.element.classList.remove("disabled");
                    self.legend.lines.forEach(function(l) {
                        if (line.series === l.series) {} else {
                            l.series.disable();
                            l.element.classList.add("disabled")
                        }
                    })
                } else {
                    self.legend.lines.forEach(function(l) {
                        l.series.enable();
                        l.element.classList.remove("disabled")
                    })
                }
                self.graph.update()
            }
        };
        if (this.legend) {
            var $ = jQuery;
            if (typeof $ != "undefined" && $(this.legend.list).sortable) {
                $(this.legend.list).sortable({
                    start: function(event, ui) {
                        ui.item.bind("no.onclick", function(event) {
                            event.preventDefault()
                        })
                    },
                    stop: function(event, ui) {
                        setTimeout(function() {
                            ui.item.unbind("no.onclick")
                        }, 250)
                    }
                })
            }
            this.legend.lines.forEach(function(l) {
                self.addAnchor(l)
            })
        }
        this._addBehavior = function() {
            this.graph.series.forEach(function(s) {
                s.disable = function() {
                    if (self.graph.series.length <= 1) {
                        throw "only one series left"
                    }
                    s.disabled = true
                };
                s.enable = function() {
                    s.disabled = false
                }
            })
        };
        this._addBehavior();
        this.updateBehaviour = function() {
            this._addBehavior()
        }
    };
    Rickshaw.namespace("Rickshaw.Graph.HoverDetail");
    Rickshaw.Graph.HoverDetail = Rickshaw.Class.create({
        initialize: function(args) {
            var graph = this.graph = args.graph;
            this.xFormatter = args.xFormatter || function(x) {
                return new Date(x * 1e3).toUTCString()
            };
            this.yFormatter = args.yFormatter || function(y) {
                return y === null ? y : y.toFixed(2)
            };
            var element = this.element = document.createElement("div");
            element.className = "detail";
            this.visible = true;
            graph.element.appendChild(element);
            this.lastEvent = null;
            this._addListeners();
            this.onShow = args.onShow;
            this.onHide = args.onHide;
            this.onRender = args.onRender;
            this.formatter = args.formatter || this.formatter
        },
        formatter: function(series, x, y, formattedX, formattedY, d) {
            return series.name + ":&nbsp;" + formattedY
        },
        update: function(e) {
            e = e || this.lastEvent;
            if (!e) return;
            this.lastEvent = e;
            if (!e.target.nodeName.match(/^(path|svg|rect|circle)$/)) return;
            var graph = this.graph;
            var eventX = e.offsetX || e.layerX;
            var eventY = e.offsetY || e.layerY;
            var j = 0;
            var points = [];
            var nearestPoint;
            this.graph.series.active().forEach(function(series) {
                var data = this.graph.stackedData[j++];
                if (!data.length) return;
                var domainX = graph.x.invert(eventX);
                var domainIndexScale = d3.scale.linear().domain([data[0].x, data.slice(-1)[0].x]).range([0, data.length - 1]);
                var approximateIndex = Math.round(domainIndexScale(domainX));
                if (approximateIndex == data.length - 1) approximateIndex--;
                var dataIndex = Math.min(approximateIndex || 0, data.length - 1);
                for (var i = approximateIndex; i < data.length - 1;) {
                    if (!data[i] || !data[i + 1]) break;
                    if (data[i].x <= domainX && data[i + 1].x > domainX) {
                        dataIndex = Math.abs(domainX - data[i].x) < Math.abs(domainX - data[i + 1].x) ? i : i + 1;
                        break
                    }
                    if (data[i + 1].x <= domainX) {
                        i++
                    } else {
                        i--
                    }
                }
                if (dataIndex < 0) dataIndex = 0;
                var value = data[dataIndex];
                var distance = Math.sqrt(Math.pow(Math.abs(graph.x(value.x) - eventX), 2) + Math.pow(Math.abs(graph.y(value.y + value.y0) - eventY), 2));
                var xFormatter = series.xFormatter || this.xFormatter;
                var yFormatter = series.yFormatter || this.yFormatter;
                var point = {
                    formattedXValue: xFormatter(value.x),
                    formattedYValue: yFormatter(series.scale ? series.scale.invert(value.y) : value.y),
                    series: series,
                    value: value,
                    distance: distance,
                    order: j,
                    name: series.name
                };
                if (!nearestPoint || distance < nearestPoint.distance) {
                    nearestPoint = point
                }
                points.push(point)
            }, this);
            if (!nearestPoint) return;
            nearestPoint.active = true;
            var domainX = nearestPoint.value.x;
            var formattedXValue = nearestPoint.formattedXValue;
            this.element.innerHTML = "";
            this.element.style.left = graph.x(domainX) + "px";
            this.visible && this.render({
                points: points,
                detail: points,
                mouseX: eventX,
                mouseY: eventY,
                formattedXValue: formattedXValue,
                domainX: domainX
            })
        },
        hide: function() {
            this.visible = false;
            this.element.classList.add("inactive");
            if (typeof this.onHide == "function") {
                this.onHide()
            }
        },
        show: function() {
            this.visible = true;
            this.element.classList.remove("inactive");
            if (typeof this.onShow == "function") {
                this.onShow()
            }
        },
        render: function(args) {
            var graph = this.graph;
            var points = args.points;
            var point = points.filter(function(p) {
                return p.active
            }).shift();
            if (point.value.y === null) return;
            var formattedXValue = point.formattedXValue;
            var formattedYValue = point.formattedYValue;
            this.element.innerHTML = "";
            this.element.style.left = graph.x(point.value.x) + "px";
            var xLabel = document.createElement("div");
            xLabel.className = "x_label";
            xLabel.innerHTML = formattedXValue;
            this.element.appendChild(xLabel);
            var item = document.createElement("div");
            item.className = "item";
            var series = point.series;
            var actualY = series.scale ? series.scale.invert(point.value.y) : point.value.y;
            item.innerHTML = this.formatter(series, point.value.x, actualY, formattedXValue, formattedYValue, point);
            item.style.top = this.graph.y(point.value.y0 + point.value.y) + "px";
            this.element.appendChild(item);
            var dot = document.createElement("div");
            dot.className = "dot";
            dot.style.top = item.style.top;
            dot.style.borderColor = series.color;
            this.element.appendChild(dot);
            if (point.active) {
                item.classList.add("active");
                dot.classList.add("active")
            }
            var alignables = [xLabel, item];
            alignables.forEach(function(el) {
                el.classList.add("left")
            });
            this.show();
            var leftAlignError = this._calcLayoutError(alignables);
            if (leftAlignError > 0) {
                alignables.forEach(function(el) {
                    el.classList.remove("left");
                    el.classList.add("right")
                });
                var rightAlignError = this._calcLayoutError(alignables);
                if (rightAlignError > leftAlignError) {
                    alignables.forEach(function(el) {
                        el.classList.remove("right");
                        el.classList.add("left")
                    })
                }
            }
            if (typeof this.onRender == "function") {
                this.onRender(args)
            }
        },
        _calcLayoutError: function(alignables) {
            var parentRect = this.element.parentNode.getBoundingClientRect();
            var error = 0;
            var alignRight = alignables.forEach(function(el) {
                var rect = el.getBoundingClientRect();
                if (!rect.width) {
                    return
                }
                if (rect.right > parentRect.right) {
                    error += rect.right - parentRect.right
                }
                if (rect.left < parentRect.left) {
                    error += parentRect.left - rect.left
                }
            });
            return error
        },
        _addListeners: function() {
            this.graph.element.addEventListener("mousemove", function(e) {
                this.visible = true;
                this.update(e)
            }.bind(this), false);
            this.graph.onUpdate(function() {
                this.update()
            }.bind(this));
            this.graph.element.addEventListener("mouseout", function(e) {
                if (e.relatedTarget && !(e.relatedTarget.compareDocumentPosition(this.graph.element) & Node.DOCUMENT_POSITION_CONTAINS)) {
                    this.hide()
                }
            }.bind(this), false)
        }
    });
    Rickshaw.namespace("Rickshaw.Graph.JSONP");
    Rickshaw.Graph.JSONP = Rickshaw.Class.create(Rickshaw.Graph.Ajax, {
        request: function() {
            jQuery.ajax({
                url: this.dataURL,
                dataType: "jsonp",
                success: this.success.bind(this),
                error: this.error.bind(this)
            })
        }
    });
    Rickshaw.namespace("Rickshaw.Graph.Legend");
    Rickshaw.Graph.Legend = Rickshaw.Class.create({
        className: "rickshaw_legend",
        initialize: function(args) {
            this.element = args.element;
            this.graph = args.graph;
            this.naturalOrder = args.naturalOrder;
            this.element.classList.add(this.className);
            this.list = document.createElement("ul");
            this.element.appendChild(this.list);
            this.render();
            this.graph.onUpdate(function() {})
        },
        render: function() {
            var self = this;
            while (this.list.firstChild) {
                this.list.removeChild(this.list.firstChild)
            }
            this.lines = [];
            var series = this.graph.series.map(function(s) {
                return s
            });
            if (!this.naturalOrder) {
                series = series.reverse()
            }
            series.forEach(function(s) {
                self.addLine(s)
            })
        },
        addLine: function(series) {
            var line = document.createElement("li");
            line.className = "line";
            if (series.disabled) {
                line.className += " disabled"
            }
            if (series.className) {
                d3.select(line).classed(series.className, true)
            }
            var swatch = document.createElement("div");
            swatch.className = "swatch";
            swatch.style.backgroundColor = series.color;
            line.appendChild(swatch);
            var label = document.createElement("span");
            label.className = "label";
            label.innerHTML = series.name;
            line.appendChild(label);
            this.list.appendChild(line);
            line.series = series;
            if (series.noLegend) {
                line.style.display = "none"
            }
            var _line = {
                element: line,
                series: series
            };
            if (this.shelving) {
                this.shelving.addAnchor(_line);
                this.shelving.updateBehaviour()
            }
            if (this.highlighter) {
                this.highlighter.addHighlightEvents(_line)
            }
            this.lines.push(_line);
            return line
        }
    });
    Rickshaw.namespace("Rickshaw.Graph.RangeSlider");
    Rickshaw.Graph.RangeSlider = Rickshaw.Class.create({
        initialize: function(args) {
            var element = this.element = args.element;
            var graph = this.graph = args.graph;
            this.slideCallbacks = [];
            this.build();
            graph.onUpdate(function() {
                this.update()
            }.bind(this))
        },
        build: function() {
            var element = this.element;
            var graph = this.graph;
            var $ = jQuery;
            var domain = graph.dataDomain();
            var self = this;
            $(function() {
                $(element).slider({
                    range: true,
                    min: domain[0],
                    max: domain[1],
                    values: [domain[0], domain[1]],
                    slide: function(event, ui) {
                        if (ui.values[1] <= ui.values[0]) return;
                        graph.window.xMin = ui.values[0];
                        graph.window.xMax = ui.values[1];
                        graph.update();
                        var domain = graph.dataDomain();
                        if (domain[0] == ui.values[0]) {
                            graph.window.xMin = undefined
                        }
                        if (domain[1] == ui.values[1]) {
                            graph.window.xMax = undefined
                        }
                        self.slideCallbacks.forEach(function(callback) {
                            callback(graph, graph.window.xMin, graph.window.xMax)
                        })
                    }
                })
            });
            $(element)[0].style.width = graph.width + "px"
        },
        update: function() {
            var element = this.element;
            var graph = this.graph;
            var $ = jQuery;
            var values = $(element).slider("option", "values");
            var domain = graph.dataDomain();
            $(element).slider("option", "min", domain[0]);
            $(element).slider("option", "max", domain[1]);
            if (graph.window.xMin == null) {
                values[0] = domain[0]
            }
            if (graph.window.xMax == null) {
                values[1] = domain[1]
            }
            $(element).slider("option", "values", values)
        },
        onSlide: function(callback) {
            this.slideCallbacks.push(callback)
        }
    });
    Rickshaw.namespace("Rickshaw.Graph.RangeSlider.Preview");
    Rickshaw.Graph.RangeSlider.Preview = Rickshaw.Class.create({
        initialize: function(args) {
            if (!args.element) throw "Rickshaw.Graph.RangeSlider.Preview needs a reference to an element";
            if (!args.graph && !args.graphs) throw "Rickshaw.Graph.RangeSlider.Preview needs a reference to an graph or an array of graphs";
            this.element = args.element;
            this.element.style.position = "relative";
            this.graphs = args.graph ? [args.graph] : args.graphs;
            this.defaults = {
                height: 75,
                width: 400,
                gripperColor: undefined,
                frameTopThickness: 3,
                frameHandleThickness: 10,
                frameColor: "#d4d4d4",
                frameOpacity: 1,
                minimumFrameWidth: 0,
                heightRatio: .2
            };
            this.heightRatio = args.heightRatio || this.defaults.heightRatio;
            this.defaults.gripperColor = d3.rgb(this.defaults.frameColor).darker().toString();
            this.configureCallbacks = [];
            this.slideCallbacks = [];
            this.previews = [];
            if (!args.width) this.widthFromGraph = true;
            if (!args.height) this.heightFromGraph = true;
            if (this.widthFromGraph || this.heightFromGraph) {
                this.graphs[0].onConfigure(function() {
                    this.configure(args);
                    this.render()
                }.bind(this))
            }
            args.width = args.width || this.graphs[0].width || this.defaults.width;
            args.height = args.height || this.graphs[0].height * this.heightRatio || this.defaults.height;
            this.configure(args);
            this.render()
        },
        onSlide: function(callback) {
            this.slideCallbacks.push(callback)
        },
        onConfigure: function(callback) {
            this.configureCallbacks.push(callback)
        },
        configure: function(args) {
            this.config = this.config || {};
            this.configureCallbacks.forEach(function(callback) {
                callback(args)
            });
            Rickshaw.keys(this.defaults).forEach(function(k) {
                this.config[k] = k in args ? args[k] : k in this.config ? this.config[k] : this.defaults[k]
            }, this);
            if ("width" in args || "height" in args) {
                if (this.widthFromGraph) {
                    this.config.width = this.graphs[0].width
                }
                if (this.heightFromGraph) {
                    this.config.height = this.graphs[0].height * this.heightRatio;
                    this.previewHeight = this.config.height
                }
                this.previews.forEach(function(preview) {
                    var height = this.previewHeight / this.graphs.length - this.config.frameTopThickness * 2;
                    var width = this.config.width - this.config.frameHandleThickness * 2;
                    preview.setSize({
                        width: width,
                        height: height
                    });
                    if (this.svg) {
                        var svgHeight = height + this.config.frameHandleThickness * 2;
                        var svgWidth = width + this.config.frameHandleThickness * 2;
                        this.svg.style("width", svgWidth + "px");
                        this.svg.style("height", svgHeight + "px")
                    }
                }, this)
            }
        },
        render: function() {
            var self = this;
            this.svg = d3.select(this.element).selectAll("svg.rickshaw_range_slider_preview").data([null]);
            this.previewHeight = this.config.height - this.config.frameTopThickness * 2;
            this.previewWidth = this.config.width - this.config.frameHandleThickness * 2;
            this.currentFrame = [0, this.previewWidth];
            var buildGraph = function(parent, index) {
                var graphArgs = Rickshaw.extend({}, parent.config);
                var height = self.previewHeight / self.graphs.length;
                var renderer = parent.renderer.name;
                Rickshaw.extend(graphArgs, {
                    element: this.appendChild(document.createElement("div")),
                    height: height,
                    width: self.previewWidth,
                    series: parent.series,
                    renderer: renderer
                });
                var graph = new Rickshaw.Graph(graphArgs);
                self.previews.push(graph);
                parent.onUpdate(function() {
                    graph.render();
                    self.render()
                });
                parent.onConfigure(function(args) {
                    delete args.height;
                    args.width = args.width - self.config.frameHandleThickness * 2;
                    graph.configure(args);
                    graph.render()
                });
                graph.render()
            };
            var graphContainer = d3.select(this.element).selectAll("div.rickshaw_range_slider_preview_container").data(this.graphs);
            var translateCommand = "translate(" + this.config.frameHandleThickness + "px, " + this.config.frameTopThickness + "px)";
            graphContainer.enter().append("div").classed("rickshaw_range_slider_preview_container", true).style("-webkit-transform", translateCommand).style("-moz-transform", translateCommand).style("-ms-transform", translateCommand).style("transform", translateCommand).each(buildGraph);
            graphContainer.exit().remove();
            var masterGraph = this.graphs[0];
            var domainScale = d3.scale.linear().domain([0, this.previewWidth]).range(masterGraph.dataDomain());
            var currentWindow = [masterGraph.window.xMin, masterGraph.window.xMax];
            this.currentFrame[0] = currentWindow[0] === undefined ? 0 : Math.round(domainScale.invert(currentWindow[0]));
            if (this.currentFrame[0] < 0) this.currentFrame[0] = 0;
            this.currentFrame[1] = currentWindow[1] === undefined ? this.previewWidth : domainScale.invert(currentWindow[1]);
            if (this.currentFrame[1] - this.currentFrame[0] < self.config.minimumFrameWidth) {
                this.currentFrame[1] = (this.currentFrame[0] || 0) + self.config.minimumFrameWidth
            }
            this.svg.enter().append("svg").classed("rickshaw_range_slider_preview", true).style("height", this.config.height + "px").style("width", this.config.width + "px").style("position", "absolute").style("top", 0);
            this._renderDimming();
            this._renderFrame();
            this._renderGrippers();
            this._renderHandles();
            this._renderMiddle();
            this._registerMouseEvents()
        },
        _renderDimming: function() {
            var element = this.svg.selectAll("path.dimming").data([null]);
            element.enter().append("path").attr("fill", "white").attr("fill-opacity", "0.7").attr("fill-rule", "evenodd").classed("dimming", true);
            var path = "";
            path += " M " + this.config.frameHandleThickness + " " + this.config.frameTopThickness;
            path += " h " + this.previewWidth;
            path += " v " + this.previewHeight;
            path += " h " + -this.previewWidth;
            path += " z ";
            path += " M " + Math.max(this.currentFrame[0], this.config.frameHandleThickness) + " " + this.config.frameTopThickness;
            path += " H " + Math.min(this.currentFrame[1] + this.config.frameHandleThickness * 2, this.previewWidth + this.config.frameHandleThickness);
            path += " v " + this.previewHeight;
            path += " H " + Math.max(this.currentFrame[0], this.config.frameHandleThickness);
            path += " z";
            element.attr("d", path)
        },
        _renderFrame: function() {
            var element = this.svg.selectAll("path.frame").data([null]);
            element.enter().append("path").attr("stroke", "white").attr("stroke-width", "1px").attr("stroke-linejoin", "round").attr("fill", this.config.frameColor).attr("fill-opacity", this.config.frameOpacity).attr("fill-rule", "evenodd").classed("frame", true);
            var path = "";
            path += " M " + this.currentFrame[0] + " 0";
            path += " H " + (this.currentFrame[1] + this.config.frameHandleThickness * 2);
            path += " V " + this.config.height;
            path += " H " + this.currentFrame[0];
            path += " z";
            path += " M " + (this.currentFrame[0] + this.config.frameHandleThickness) + " " + this.config.frameTopThickness;
            path += " H " + (this.currentFrame[1] + this.config.frameHandleThickness);
            path += " v " + this.previewHeight;
            path += " H " + (this.currentFrame[0] + this.config.frameHandleThickness);
            path += " z";
            element.attr("d", path)
        },
        _renderGrippers: function() {
            var gripper = this.svg.selectAll("path.gripper").data([null]);
            gripper.enter().append("path").attr("stroke", this.config.gripperColor).classed("gripper", true);
            var path = "";
            [.4, .6].forEach(function(spacing) {
                path += " M " + Math.round(this.currentFrame[0] + this.config.frameHandleThickness * spacing) + " " + Math.round(this.config.height * .3);
                path += " V " + Math.round(this.config.height * .7);
                path += " M " + Math.round(this.currentFrame[1] + this.config.frameHandleThickness * (1 + spacing)) + " " + Math.round(this.config.height * .3);
                path += " V " + Math.round(this.config.height * .7)
            }.bind(this));
            gripper.attr("d", path)
        },
        _renderHandles: function() {
            var leftHandle = this.svg.selectAll("rect.left_handle").data([null]);
            leftHandle.enter().append("rect").attr("width", this.config.frameHandleThickness).style("cursor", "ew-resize").style("fill-opacity", "0").classed("left_handle", true);
            leftHandle.attr("x", this.currentFrame[0]).attr("height", this.config.height);
            var rightHandle = this.svg.selectAll("rect.right_handle").data([null]);
            rightHandle.enter().append("rect").attr("width", this.config.frameHandleThickness).style("cursor", "ew-resize").style("fill-opacity", "0").classed("right_handle", true);
            rightHandle.attr("x", this.currentFrame[1] + this.config.frameHandleThickness).attr("height", this.config.height)
        },
        _renderMiddle: function() {
            var middleHandle = this.svg.selectAll("rect.middle_handle").data([null]);
            middleHandle.enter().append("rect").style("cursor", "move").style("fill-opacity", "0").classed("middle_handle", true);
            middleHandle.attr("width", Math.max(0, this.currentFrame[1] - this.currentFrame[0])).attr("x", this.currentFrame[0] + this.config.frameHandleThickness).attr("height", this.config.height)
        },
        _registerMouseEvents: function() {
            var element = d3.select(this.element);
            var drag = {
                target: null,
                start: null,
                stop: null,
                left: false,
                right: false,
                rigid: false
            };
            var self = this;

            function onMousemove(datum, index) {
                drag.stop = self._getClientXFromEvent(d3.event, drag);
                var distanceTraveled = drag.stop - drag.start;
                var frameAfterDrag = self.frameBeforeDrag.slice(0);
                var minimumFrameWidth = self.config.minimumFrameWidth;
                if (drag.rigid) {
                    minimumFrameWidth = self.frameBeforeDrag[1] - self.frameBeforeDrag[0]
                }
                if (drag.left) {
                    frameAfterDrag[0] = Math.max(frameAfterDrag[0] + distanceTraveled, 0)
                }
                if (drag.right) {
                    frameAfterDrag[1] = Math.min(frameAfterDrag[1] + distanceTraveled, self.previewWidth)
                }
                var currentFrameWidth = frameAfterDrag[1] - frameAfterDrag[0];
                if (currentFrameWidth <= minimumFrameWidth) {
                    if (drag.left) {
                        frameAfterDrag[0] = frameAfterDrag[1] - minimumFrameWidth
                    }
                    if (drag.right) {
                        frameAfterDrag[1] = frameAfterDrag[0] + minimumFrameWidth
                    }
                    if (frameAfterDrag[0] <= 0) {
                        frameAfterDrag[1] -= frameAfterDrag[0];
                        frameAfterDrag[0] = 0
                    }
                    if (frameAfterDrag[1] >= self.previewWidth) {
                        frameAfterDrag[0] -= frameAfterDrag[1] - self.previewWidth;
                        frameAfterDrag[1] = self.previewWidth
                    }
                }
                self.graphs.forEach(function(graph) {
                    var domainScale = d3.scale.linear().interpolate(d3.interpolateNumber).domain([0, self.previewWidth]).range(graph.dataDomain());
                    var windowAfterDrag = [domainScale(frameAfterDrag[0]), domainScale(frameAfterDrag[1])];
                    self.slideCallbacks.forEach(function(callback) {
                        callback(graph, windowAfterDrag[0], windowAfterDrag[1])
                    });
                    if (frameAfterDrag[0] === 0) {
                        windowAfterDrag[0] = undefined
                    }
                    if (frameAfterDrag[1] === self.previewWidth) {
                        windowAfterDrag[1] = undefined
                    }
                    graph.window.xMin = windowAfterDrag[0];
                    graph.window.xMax = windowAfterDrag[1];
                    graph.update()
                })
            }

            function onMousedown() {
                drag.target = d3.event.target;
                drag.start = self._getClientXFromEvent(d3.event, drag);
                self.frameBeforeDrag = self.currentFrame.slice();
                d3.event.preventDefault ? d3.event.preventDefault() : d3.event.returnValue = false;
                d3.select(document).on("mousemove.rickshaw_range_slider_preview", onMousemove);
                d3.select(document).on("mouseup.rickshaw_range_slider_preview", onMouseup);
                d3.select(document).on("touchmove.rickshaw_range_slider_preview", onMousemove);
                d3.select(document).on("touchend.rickshaw_range_slider_preview", onMouseup);
                d3.select(document).on("touchcancel.rickshaw_range_slider_preview", onMouseup)
            }

            function onMousedownLeftHandle(datum, index) {
                drag.left = true;
                onMousedown()
            }

            function onMousedownRightHandle(datum, index) {
                drag.right = true;
                onMousedown()
            }

            function onMousedownMiddleHandle(datum, index) {
                drag.left = true;
                drag.right = true;
                drag.rigid = true;
                onMousedown()
            }

            function onMouseup(datum, index) {
                d3.select(document).on("mousemove.rickshaw_range_slider_preview", null);
                d3.select(document).on("mouseup.rickshaw_range_slider_preview", null);
                d3.select(document).on("touchmove.rickshaw_range_slider_preview", null);
                d3.select(document).on("touchend.rickshaw_range_slider_preview", null);
                d3.select(document).on("touchcancel.rickshaw_range_slider_preview", null);
                delete self.frameBeforeDrag;
                drag.left = false;
                drag.right = false;
                drag.rigid = false
            }
            element.select("rect.left_handle").on("mousedown", onMousedownLeftHandle);
            element.select("rect.right_handle").on("mousedown", onMousedownRightHandle);
            element.select("rect.middle_handle").on("mousedown", onMousedownMiddleHandle);
            element.select("rect.left_handle").on("touchstart", onMousedownLeftHandle);
            element.select("rect.right_handle").on("touchstart", onMousedownRightHandle);
            element.select("rect.middle_handle").on("touchstart", onMousedownMiddleHandle)
        },
        _getClientXFromEvent: function(event, drag) {
            switch (event.type) {
                case "touchstart":
                case "touchmove":
                    var touchList = event.changedTouches;
                    var touch = null;
                    for (var touchIndex = 0; touchIndex < touchList.length; touchIndex++) {
                        if (touchList[touchIndex].target === drag.target) {
                            touch = touchList[touchIndex];
                            break
                        }
                    }
                    return touch !== null ? touch.clientX : undefined;
                default:
                    return event.clientX
            }
        }
    });
    Rickshaw.namespace("Rickshaw.Graph.Renderer");
    Rickshaw.Graph.Renderer = Rickshaw.Class.create({
        initialize: function(args) {
            this.graph = args.graph;
            this.tension = args.tension || this.tension;
            this.configure(args)
        },
        seriesPathFactory: function() {},
        seriesStrokeFactory: function() {},
        defaults: function() {
            return {
                tension: .8,
                strokeWidth: 2,
                unstack: true,
                padding: {
                    top: .01,
                    right: 0,
                    bottom: .01,
                    left: 0
                },
                stroke: false,
                fill: false
            }
        },
        domain: function(data) {
            var stackedData = data || this.graph.stackedData || this.graph.stackData();
            var xMin = +Infinity;
            var xMax = -Infinity;
            var yMin = +Infinity;
            var yMax = -Infinity;
            stackedData.forEach(function(series) {
                series.forEach(function(d) {
                    if (d.y == null) return;
                    var y = d.y + d.y0;
                    if (y < yMin) yMin = y;
                    if (y > yMax) yMax = y
                });
                if (!series.length) return;
                if (series[0].x < xMin) xMin = series[0].x;
                if (series[series.length - 1].x > xMax) xMax = series[series.length - 1].x
            });
            xMin -= (xMax - xMin) * this.padding.left;
            xMax += (xMax - xMin) * this.padding.right;
            yMin = this.graph.min === "auto" ? yMin : this.graph.min || 0;
            yMax = this.graph.max === undefined ? yMax : this.graph.max;
            if (this.graph.min === "auto" || yMin < 0) {
                yMin -= (yMax - yMin) * this.padding.bottom
            }
            if (this.graph.max === undefined) {
                yMax += (yMax - yMin) * this.padding.top
            }
            return {
                x: [xMin, xMax],
                y: [yMin, yMax]
            }
        },
        render: function(args) {
            args = args || {};
            var graph = this.graph;
            var series = args.series || graph.series;
            var vis = args.vis || graph.vis;
            vis.selectAll("*").remove();
            var data = series.filter(function(s) {
                return !s.disabled
            }).map(function(s) {
                return s.stack
            });
            var pathNodes = vis.selectAll("path.path").data(data).enter().append("svg:path").classed("path", true).attr("d", this.seriesPathFactory());
            if (this.stroke) {
                var strokeNodes = vis.selectAll("path.stroke").data(data).enter().append("svg:path").classed("stroke", true).attr("d", this.seriesStrokeFactory())
            }
            var i = 0;
            series.forEach(function(series) {
                if (series.disabled) return;
                series.path = pathNodes[0][i];
                if (this.stroke) series.stroke = strokeNodes[0][i];
                this._styleSeries(series);
                i++
            }, this)
        },
        _styleSeries: function(series) {
            var fill = this.fill ? series.color : "none";
            var stroke = this.stroke ? series.color : "none";
            series.path.setAttribute("fill", fill);
            series.path.setAttribute("stroke", stroke);
            series.path.setAttribute("stroke-width", this.strokeWidth);
            if (series.className) {
                d3.select(series.path).classed(series.className, true)
            }
            if (series.className && this.stroke) {
                d3.select(series.stroke).classed(series.className, true)
            }
        },
        configure: function(args) {
            args = args || {};
            Rickshaw.keys(this.defaults()).forEach(function(key) {
                if (!args.hasOwnProperty(key)) {
                    this[key] = this[key] || this.graph[key] || this.defaults()[key];
                    return
                }
                if (typeof this.defaults()[key] == "object") {
                    Rickshaw.keys(this.defaults()[key]).forEach(function(k) {
                        this[key][k] = args[key][k] !== undefined ? args[key][k] : this[key][k] !== undefined ? this[key][k] : this.defaults()[key][k]
                    }, this)
                } else {
                    this[key] = args[key] !== undefined ? args[key] : this[key] !== undefined ? this[key] : this.graph[key] !== undefined ? this.graph[key] : this.defaults()[key]
                }
            }, this)
        },
        setStrokeWidth: function(strokeWidth) {
            if (strokeWidth !== undefined) {
                this.strokeWidth = strokeWidth
            }
        },
        setTension: function(tension) {
            if (tension !== undefined) {
                this.tension = tension
            }
        }
    });
    Rickshaw.namespace("Rickshaw.Graph.Renderer.Line");
    Rickshaw.Graph.Renderer.Line = Rickshaw.Class.create(Rickshaw.Graph.Renderer, {
        name: "line",
        defaults: function($super) {
            return Rickshaw.extend($super(), {
                unstack: true,
                fill: false,
                stroke: true
            })
        },
        seriesPathFactory: function() {
            var graph = this.graph;
            var factory = d3.svg.line().x(function(d) {
                return graph.x(d.x)
            }).y(function(d) {
                return graph.y(d.y)
            }).interpolate(this.graph.interpolation).tension(this.tension);
            factory.defined && factory.defined(function(d) {
                return d.y !== null
            });
            return factory
        }
    });
    Rickshaw.namespace("Rickshaw.Graph.Renderer.Stack");
    Rickshaw.Graph.Renderer.Stack = Rickshaw.Class.create(Rickshaw.Graph.Renderer, {
        name: "stack",
        defaults: function($super) {
            return Rickshaw.extend($super(), {
                fill: true,
                stroke: false,
                unstack: false
            })
        },
        seriesPathFactory: function() {
            var graph = this.graph;
            var factory = d3.svg.area().x(function(d) {
                return graph.x(d.x)
            }).y0(function(d) {
                return graph.y(d.y0)
            }).y1(function(d) {
                return graph.y(d.y + d.y0)
            }).interpolate(this.graph.interpolation).tension(this.tension);
            factory.defined && factory.defined(function(d) {
                return d.y !== null
            });
            return factory
        }
    });
    Rickshaw.namespace("Rickshaw.Graph.Renderer.Bar");
    Rickshaw.Graph.Renderer.Bar = Rickshaw.Class.create(Rickshaw.Graph.Renderer, {
        name: "bar",
        defaults: function($super) {
            var defaults = Rickshaw.extend($super(), {
                gapSize: .05,
                unstack: false
            });
            delete defaults.tension;
            return defaults
        },
        initialize: function($super, args) {
            args = args || {};
            this.gapSize = args.gapSize || this.gapSize;
            $super(args)
        },
        domain: function($super) {
            var domain = $super();
            var frequentInterval = this._frequentInterval(this.graph.stackedData.slice(-1).shift());
            domain.x[1] += Number(frequentInterval.magnitude);
            return domain
        },
        barWidth: function(series) {
            var frequentInterval = this._frequentInterval(series.stack);
            var barWidth = this.graph.x.magnitude(frequentInterval.magnitude) * (1 - this.gapSize);
            return barWidth
        },
        render: function(args) {
            args = args || {};
            var graph = this.graph;
            var series = args.series || graph.series;
            var vis = args.vis || graph.vis;
            vis.selectAll("*").remove();
            var barWidth = this.barWidth(series.active()[0]);
            var barXOffset = 0;
            var activeSeriesCount = series.filter(function(s) {
                return !s.disabled
            }).length;
            var seriesBarWidth = this.unstack ? barWidth / activeSeriesCount : barWidth;
            var transform = function(d) {
                var matrix = [1, 0, 0, d.y < 0 ? -1 : 1, 0, d.y < 0 ? graph.y.magnitude(Math.abs(d.y)) * 2 : 0];
                return "matrix(" + matrix.join(",") + ")"
            };
            series.forEach(function(series) {
                if (series.disabled) return;
                var barWidth = this.barWidth(series);
                var nodes = vis.selectAll("path").data(series.stack.filter(function(d) {
                    return d.y !== null
                })).enter().append("svg:rect").attr("x", function(d) {
                    return graph.x(d.x) + barXOffset
                }).attr("y", function(d) {
                    return graph.y(d.y0 + Math.abs(d.y)) * (d.y < 0 ? -1 : 1)
                }).attr("width", seriesBarWidth).attr("height", function(d) {
                    return graph.y.magnitude(Math.abs(d.y))
                }).attr("transform", transform);
                Array.prototype.forEach.call(nodes[0], function(n) {
                    n.setAttribute("fill", series.color)
                });
                if (this.unstack) barXOffset += seriesBarWidth
            }, this)
        },
        _frequentInterval: function(data) {
            var intervalCounts = {};
            for (var i = 0; i < data.length - 1; i++) {
                var interval = data[i + 1].x - data[i].x;
                intervalCounts[interval] = intervalCounts[interval] || 0;
                intervalCounts[interval]++
            }
            var frequentInterval = {
                count: 0,
                magnitude: 1
            };
            Rickshaw.keys(intervalCounts).forEach(function(i) {
                if (frequentInterval.count < intervalCounts[i]) {
                    frequentInterval = {
                        count: intervalCounts[i],
                        magnitude: i
                    }
                }
            });
            return frequentInterval
        }
    });
    Rickshaw.namespace("Rickshaw.Graph.Renderer.Area");
    Rickshaw.Graph.Renderer.Area = Rickshaw.Class.create(Rickshaw.Graph.Renderer, {
        name: "area",
        defaults: function($super) {
            return Rickshaw.extend($super(), {
                unstack: false,
                fill: false,
                stroke: false
            })
        },
        seriesPathFactory: function() {
            var graph = this.graph;
            var factory = d3.svg.area().x(function(d) {
                return graph.x(d.x)
            }).y0(function(d) {
                return graph.y(d.y0)
            }).y1(function(d) {
                return graph.y(d.y + d.y0)
            }).interpolate(graph.interpolation).tension(this.tension);
            factory.defined && factory.defined(function(d) {
                return d.y !== null
            });
            return factory
        },
        seriesStrokeFactory: function() {
            var graph = this.graph;
            var factory = d3.svg.line().x(function(d) {
                return graph.x(d.x)
            }).y(function(d) {
                return graph.y(d.y + d.y0)
            }).interpolate(graph.interpolation).tension(this.tension);
            factory.defined && factory.defined(function(d) {
                return d.y !== null
            });
            return factory
        },
        render: function(args) {
            args = args || {};
            var graph = this.graph;
            var series = args.series || graph.series;
            var vis = args.vis || graph.vis;
            vis.selectAll("*").remove();
            var method = this.unstack ? "append" : "insert";
            var data = series.filter(function(s) {
                return !s.disabled
            }).map(function(s) {
                return s.stack
            });
            var nodes = vis.selectAll("path").data(data).enter()[method]("svg:g", "g");
            nodes.append("svg:path").attr("d", this.seriesPathFactory()).attr("class", "area");
            if (this.stroke) {
                nodes.append("svg:path").attr("d", this.seriesStrokeFactory()).attr("class", "line")
            }
            var i = 0;
            series.forEach(function(series) {
                if (series.disabled) return;
                series.path = nodes[0][i++];
                this._styleSeries(series)
            }, this)
        },
        _styleSeries: function(series) {
            if (!series.path) return;
            d3.select(series.path).select(".area").attr("fill", series.color);
            if (this.stroke) {
                d3.select(series.path).select(".line").attr("fill", "none").attr("stroke", series.stroke || d3.interpolateRgb(series.color, "black")(.125)).attr("stroke-width", this.strokeWidth)
            }
            if (series.className) {
                series.path.setAttribute("class", series.className)
            }
        }
    });
    Rickshaw.namespace("Rickshaw.Graph.Renderer.ScatterPlot");
    Rickshaw.Graph.Renderer.ScatterPlot = Rickshaw.Class.create(Rickshaw.Graph.Renderer, {
        name: "scatterplot",
        defaults: function($super) {
            return Rickshaw.extend($super(), {
                unstack: true,
                fill: true,
                stroke: false,
                padding: {
                    top: .01,
                    right: .01,
                    bottom: .01,
                    left: .01
                },
                dotSize: 4
            })
        },
        initialize: function($super, args) {
            $super(args)
        },
        render: function(args) {
            args = args || {};
            var graph = this.graph;
            var series = args.series || graph.series;
            var vis = args.vis || graph.vis;
            var dotSize = this.dotSize;
            vis.selectAll("*").remove();
            series.forEach(function(series) {
                if (series.disabled) return;
                var nodes = vis.selectAll("path").data(series.stack.filter(function(d) {
                    return d.y !== null
                })).enter().append("svg:circle").attr("cx", function(d) {
                    return graph.x(d.x)
                }).attr("cy", function(d) {
                    return graph.y(d.y)
                }).attr("r", function(d) {
                    return "r" in d ? d.r : dotSize
                });
                if (series.className) {
                    nodes.classed(series.className, true)
                }
                Array.prototype.forEach.call(nodes[0], function(n) {
                    n.setAttribute("fill", series.color)
                })
            }, this)
        }
    });
    Rickshaw.namespace("Rickshaw.Graph.Renderer.Multi");
    Rickshaw.Graph.Renderer.Multi = Rickshaw.Class.create(Rickshaw.Graph.Renderer, {
        name: "multi",
        initialize: function($super, args) {
            $super(args)
        },
        defaults: function($super) {
            return Rickshaw.extend($super(), {
                unstack: true,
                fill: false,
                stroke: true
            })
        },
        configure: function($super, args) {
            args = args || {};
            this.config = args;
            $super(args)
        },
        domain: function($super) {
            this.graph.stackData();
            var domains = [];
            var groups = this._groups();
            this._stack(groups);
            groups.forEach(function(group) {
                var data = group.series.filter(function(s) {
                    return !s.disabled
                }).map(function(s) {
                    return s.stack
                });
                if (!data.length) return;
                var domain = null;
                if (group.renderer && group.renderer.domain) {
                    domain = group.renderer.domain(data)
                } else {
                    domain = $super(data)
                }
                domains.push(domain)
            });
            var xMin = d3.min(domains.map(function(d) {
                return d.x[0]
            }));
            var xMax = d3.max(domains.map(function(d) {
                return d.x[1]
            }));
            var yMin = d3.min(domains.map(function(d) {
                return d.y[0]
            }));
            var yMax = d3.max(domains.map(function(d) {
                return d.y[1]
            }));
            return {
                x: [xMin, xMax],
                y: [yMin, yMax]
            }
        },
        _groups: function() {
            var graph = this.graph;
            var renderGroups = {};
            graph.series.forEach(function(series) {
                if (series.disabled) return;
                if (!renderGroups[series.renderer]) {
                    var ns = "http://www.w3.org/2000/svg";
                    var vis = document.createElementNS(ns, "g");
                    graph.vis[0][0].appendChild(vis);
                    var renderer = graph._renderers[series.renderer];
                    var config = {};
                    var defaults = [this.defaults(), renderer.defaults(), this.config, this.graph];
                    defaults.forEach(function(d) {
                        Rickshaw.extend(config, d)
                    });
                    renderer.configure(config);
                    renderGroups[series.renderer] = {
                        renderer: renderer,
                        series: [],
                        vis: d3.select(vis)
                    }
                }
                renderGroups[series.renderer].series.push(series)
            }, this);
            var groups = [];
            Object.keys(renderGroups).forEach(function(key) {
                var group = renderGroups[key];
                groups.push(group)
            });
            return groups
        },
        _stack: function(groups) {
            groups.forEach(function(group) {
                var series = group.series.filter(function(series) {
                    return !series.disabled
                });
                var data = series.map(function(series) {
                    return series.stack
                });
                if (!group.renderer.unstack) {
                    var layout = d3.layout.stack();
                    var stackedData = Rickshaw.clone(layout(data));
                    series.forEach(function(series, index) {
                        series._stack = Rickshaw.clone(stackedData[index])
                    })
                }
            }, this);
            return groups
        },
        render: function() {
            this.graph.series.forEach(function(series) {
                if (!series.renderer) {
                    throw new Error("Each series needs a renderer for graph 'multi' renderer")
                }
            });
            this.graph.vis.selectAll("*").remove();
            var groups = this._groups();
            groups = this._stack(groups);
            groups.forEach(function(group) {
                var series = group.series.filter(function(series) {
                    return !series.disabled
                });
                series.active = function() {
                    return series
                };
                group.renderer.render({
                    series: series,
                    vis: group.vis
                });
                series.forEach(function(s) {
                    s.stack = s._stack || s.stack || s.data
                })
            })
        }
    });
    Rickshaw.namespace("Rickshaw.Graph.Renderer.LinePlot");
    Rickshaw.Graph.Renderer.LinePlot = Rickshaw.Class.create(Rickshaw.Graph.Renderer, {
        name: "lineplot",
        defaults: function($super) {
            return Rickshaw.extend($super(), {
                unstack: true,
                fill: false,
                stroke: true,
                padding: {
                    top: .01,
                    right: .01,
                    bottom: .01,
                    left: .01
                },
                dotSize: 3,
                strokeWidth: 2
            })
        },
        seriesPathFactory: function() {
            var graph = this.graph;
            var factory = d3.svg.line().x(function(d) {
                return graph.x(d.x)
            }).y(function(d) {
                return graph.y(d.y)
            }).interpolate(this.graph.interpolation).tension(this.tension);
            factory.defined && factory.defined(function(d) {
                return d.y !== null
            });
            return factory
        },
        render: function(args) {
            args = args || {};
            var graph = this.graph;
            var series = args.series || graph.series;
            var vis = args.vis || graph.vis;
            var dotSize = this.dotSize;
            vis.selectAll("*").remove();
            var data = series.filter(function(s) {
                return !s.disabled
            }).map(function(s) {
                return s.stack
            });
            var nodes = vis.selectAll("path").data(data).enter().append("svg:path").attr("d", this.seriesPathFactory());
            var i = 0;
            series.forEach(function(series) {
                if (series.disabled) return;
                series.path = nodes[0][i++];
                this._styleSeries(series)
            }, this);
            series.forEach(function(series) {
                if (series.disabled) return;
                var nodes = vis.selectAll("x").data(series.stack.filter(function(d) {
                    return d.y !== null
                })).enter().append("svg:circle").attr("cx", function(d) {
                    return graph.x(d.x)
                }).attr("cy", function(d) {
                    return graph.y(d.y)
                }).attr("r", function(d) {
                    return "r" in d ? d.r : dotSize
                });
                Array.prototype.forEach.call(nodes[0], function(n) {
                    if (!n) return;
                    n.setAttribute("data-color", series.color);
                    n.setAttribute("fill", "white");
                    n.setAttribute("stroke", series.color);
                    n.setAttribute("stroke-width", this.strokeWidth)
                }.bind(this))
            }, this)
        }
    });
    Rickshaw.namespace("Rickshaw.Graph.Smoother");
    Rickshaw.Graph.Smoother = Rickshaw.Class.create({
        initialize: function(args) {
            this.graph = args.graph;
            this.element = args.element;
            this.aggregationScale = 1;
            this.build();
            this.graph.stackData.hooks.data.push({
                name: "smoother",
                orderPosition: 50,
                f: this.transformer.bind(this)
            })
        },
        build: function() {
            var self = this;
            var $ = jQuery;
            if (this.element) {
                $(function() {
                    $(self.element).slider({
                        min: 1,
                        max: 100,
                        slide: function(event, ui) {
                            self.setScale(ui.value)
                        }
                    })
                })
            }
        },
        setScale: function(scale) {
            if (scale < 1) {
                throw "scale out of range: " + scale
            }
            this.aggregationScale = scale;
            this.graph.update()
        },
        transformer: function(data) {
            if (this.aggregationScale == 1) return data;
            var aggregatedData = [];
            data.forEach(function(seriesData) {
                var aggregatedSeriesData = [];
                while (seriesData.length) {
                    var avgX = 0,
                        avgY = 0;
                    var slice = seriesData.splice(0, this.aggregationScale);
                    slice.forEach(function(d) {
                        avgX += d.x / slice.length;
                        avgY += d.y / slice.length
                    });
                    aggregatedSeriesData.push({
                        x: avgX,
                        y: avgY
                    })
                }
                aggregatedData.push(aggregatedSeriesData)
            }.bind(this));
            return aggregatedData
        }
    });
    Rickshaw.namespace("Rickshaw.Graph.Socketio");
    Rickshaw.Graph.Socketio = Rickshaw.Class.create(Rickshaw.Graph.Ajax, {
        request: function() {
            var socket = io.connect(this.dataURL);
            var self = this;
            socket.on("rickshaw", function(data) {
                self.success(data)
            })
        }
    });
    Rickshaw.namespace("Rickshaw.Series");
    Rickshaw.Series = Rickshaw.Class.create(Array, {
        initialize: function(data, palette, options) {
            options = options || {};
            this.palette = new Rickshaw.Color.Palette(palette);
            this.timeBase = typeof options.timeBase === "undefined" ? Math.floor((new Date).getTime() / 1e3) : options.timeBase;
            var timeInterval = typeof options.timeInterval == "undefined" ? 1e3 : options.timeInterval;
            this.setTimeInterval(timeInterval);
            if (data && typeof data == "object" && Array.isArray(data)) {
                data.forEach(function(item) {
                    this.addItem(item)
                }, this)
            }
        },
        addItem: function(item) {
            if (typeof item.name === "undefined") {
                throw "addItem() needs a name"
            }
            item.color = item.color || this.palette.color(item.name);
            item.data = item.data || [];
            if (item.data.length === 0 && this.length && this.getIndex() > 0) {
                this[0].data.forEach(function(plot) {
                    item.data.push({
                        x: plot.x,
                        y: 0
                    })
                })
            } else if (item.data.length === 0) {
                item.data.push({
                    x: this.timeBase - (this.timeInterval || 0),
                    y: 0
                })
            }
            this.push(item);
            if (this.legend) {
                this.legend.addLine(this.itemByName(item.name))
            }
        },
        addData: function(data, x) {
            var index = this.getIndex();
            Rickshaw.keys(data).forEach(function(name) {
                if (!this.itemByName(name)) {
                    this.addItem({
                        name: name
                    })
                }
            }, this);
            this.forEach(function(item) {
                item.data.push({
                    x: x || (index * this.timeInterval || 1) + this.timeBase,
                    y: data[item.name] || 0
                })
            }, this)
        },
        getIndex: function() {
            return this[0] && this[0].data && this[0].data.length ? this[0].data.length : 0
        },
        itemByName: function(name) {
            for (var i = 0; i < this.length; i++) {
                if (this[i].name == name) return this[i]
            }
        },
        setTimeInterval: function(iv) {
            this.timeInterval = iv / 1e3
        },
        setTimeBase: function(t) {
            this.timeBase = t
        },
        dump: function() {
            var data = {
                timeBase: this.timeBase,
                timeInterval: this.timeInterval,
                items: []
            };
            this.forEach(function(item) {
                var newItem = {
                    color: item.color,
                    name: item.name,
                    data: []
                };
                item.data.forEach(function(plot) {
                    newItem.data.push({
                        x: plot.x,
                        y: plot.y
                    })
                });
                data.items.push(newItem)
            });
            return data
        },
        load: function(data) {
            if (data.timeInterval) {
                this.timeInterval = data.timeInterval
            }
            if (data.timeBase) {
                this.timeBase = data.timeBase
            }
            if (data.items) {
                data.items.forEach(function(item) {
                    this.push(item);
                    if (this.legend) {
                        this.legend.addLine(this.itemByName(item.name))
                    }
                }, this)
            }
        }
    });
    Rickshaw.Series.zeroFill = function(series) {
        Rickshaw.Series.fill(series, 0)
    };
    Rickshaw.Series.fill = function(series, fill) {
        var x;
        var i = 0;
        var data = series.map(function(s) {
            return s.data
        });
        while (i < Math.max.apply(null, data.map(function(d) {
                return d.length
            }))) {
            x = Math.min.apply(null, data.filter(function(d) {
                return d[i]
            }).map(function(d) {
                return d[i].x
            }));
            data.forEach(function(d) {
                if (!d[i] || d[i].x != x) {
                    d.splice(i, 0, {
                        x: x,
                        y: fill
                    })
                }
            });
            i++
        }
    };
    Rickshaw.namespace("Rickshaw.Series.FixedDuration");
    Rickshaw.Series.FixedDuration = Rickshaw.Class.create(Rickshaw.Series, {
        initialize: function(data, palette, options) {
            options = options || {};
            if (typeof options.timeInterval === "undefined") {
                throw new Error("FixedDuration series requires timeInterval")
            }
            if (typeof options.maxDataPoints === "undefined") {
                throw new Error("FixedDuration series requires maxDataPoints")
            }
            this.palette = new Rickshaw.Color.Palette(palette);
            this.timeBase = typeof options.timeBase === "undefined" ? Math.floor((new Date).getTime() / 1e3) : options.timeBase;
            this.setTimeInterval(options.timeInterval);
            if (this[0] && this[0].data && this[0].data.length) {
                this.currentSize = this[0].data.length;
                this.currentIndex = this[0].data.length
            } else {
                this.currentSize = 0;
                this.currentIndex = 0
            }
            this.maxDataPoints = options.maxDataPoints;
            if (data && typeof data == "object" && Array.isArray(data)) {
                data.forEach(function(item) {
                    this.addItem(item)
                }, this);
                this.currentSize += 1;
                this.currentIndex += 1
            }
            this.timeBase -= (this.maxDataPoints - this.currentSize) * this.timeInterval;
            if (typeof this.maxDataPoints !== "undefined" && this.currentSize < this.maxDataPoints) {
                for (var i = this.maxDataPoints - this.currentSize - 1; i > 1; i--) {
                    this.currentSize += 1;
                    this.currentIndex += 1;
                    this.forEach(function(item) {
                        item.data.unshift({
                            x: ((i - 1) * this.timeInterval || 1) + this.timeBase,
                            y: 0,
                            i: i
                        })
                    }, this)
                }
            }
        },
        addData: function($super, data, x) {
            $super(data, x);
            this.currentSize += 1;
            this.currentIndex += 1;
            if (this.maxDataPoints !== undefined) {
                while (this.currentSize > this.maxDataPoints) {
                    this.dropData()
                }
            }
        },
        dropData: function() {
            this.forEach(function(item) {
                item.data.splice(0, 1)
            });
            this.currentSize -= 1
        },
        getIndex: function() {
            return this.currentIndex
        }
    });
    return Rickshaw
});